package org.hxzon.nioserver.impl;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.hxzon.nioserver.BufferFactory;
import org.hxzon.nioserver.InputQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class LengthInputQueueImpl implements InputQueue {
    private static final Logger logger = LoggerFactory.getLogger(LengthInputQueueImpl.class);
    //
    private final BufferFactory bufferFactory;
    //
    private final Queue<ByteBuffer> bufferQueue;
    private ByteBuffer bufForReadCurrentData = null;
    //
    private final ByteBuffer bufForReadLen;
    private int lenExpect = -1;//-1 is first

    public LengthInputQueueImpl(BufferFactory bufferFactory) {
        this.bufferFactory = bufferFactory;
        this.bufferQueue = new ConcurrentLinkedQueue<ByteBuffer>();
        this.bufForReadLen = ByteBuffer.allocate(4);
    }

    //----------------
    public synchronized ByteBuffer nextByteBuffer() {
        return bufferQueue.poll();
    }

    //---------------
    public synchronized int fillFrom(ByteChannel channel) throws IOException {
        logger.trace("fillFrom thread");
        while (true) {
            if (lenExpect == -1) {
                if (!initLen(channel)) {
                    break;//wait for next fillFrom
                    //return -1;//?
                }
            }
            readData(channel);
            //
            if (bufForReadCurrentData.hasRemaining()) {
                readDataNotComplete();
                break;//wait for next fillFrom
                //return -1;//?
            } else {
                readDataComplete();
                //try read next buffer
            }
        }
        return 0;
    }

    private boolean initLen(ByteChannel channel) throws IOException {
        int count = -1;
        do {
            count = channel.read(bufForReadLen);
        } while (count > 0);
        //
        if (bufForReadLen.hasRemaining()) {//not fill 4 bytes
            return false;
        }
        bufForReadLen.flip();
        lenExpect = bufForReadLen.getInt();
        bufForReadLen.clear();
        logger.trace("len expect:" + lenExpect);
        bufForReadCurrentData = bufferFactory.newBuffer(lenExpect);
        return true;
    }

    private void readData(ByteChannel channel) throws IOException {
        int count = -1;
        do {
            count = channel.read(bufForReadCurrentData);
            logger.trace("read count:" + count);
        } while (count > 0);
    }

    private void readDataComplete() {
        logger.trace("read complete");
        //can write only after read complete
        //Util.registerChannel(key.selector(), channel, SelectionKey.OP_WRITE, this);
        bufForReadCurrentData.flip();
        bufferQueue.add(bufForReadCurrentData);
        //
        //reset for new request
        lenExpect = -1;
    }

    private void readDataNotComplete() {
        logger.trace("read not complete");
        //will be remove,so register again
        //Util.registerChannel(key.selector(), channel, SelectionKey.OP_READ, this);
        //key.selector().select(Config.timeout_select);//?
        //key.interestOps(key.interestOps() | SelectionKey.OP_READ);//?
        //key.selector().wakeup();
    }

}
